Skip to content

feat: link package names to npm in html report - #1021

Merged
sonukapoor merged 6 commits into
OWASP:mainfrom
bharatmalik-cs:fix/npm-links-in-report
Aug 24, 2026
Merged

sonukapoor merged 6 commits into
OWASP:mainfrom
bharatmalik-cs:fix/npm-links-in-report

Conversation

@bharatmalik-cs

@bharatmalik-cs bharatmalik-cs commented Aug 20, 2026

Copy link
Copy Markdown

Fixes #1020

Wraps package names in dependency path chips, chain-proof hops, and finding header with links to their npm page (https://www.npmjs.com/package/).

@sonukapoor sonukapoor left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good first PR - the approach is right and escapeHtml is used correctly throughout. Three small things to fix before we merge.

Comment thread src/output/html-reporter.ts Outdated
: `<span class="fix-hint none" title="No known fix — consider replacing this package">⚠ No fix</span>`;

const depPathHtml = finding.dependencyPaths.length > 0
const depPathHtml = finding.dependencyPaths.length > 0

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const depPathHtml lost its indent - should be 2 spaces in to match the surrounding code.

Comment thread src/output/html-reporter.ts Outdated
return `<span class="dep-node${isLast ? " vulnerable" : ""}">${escapeHtml(node)}</span>${isLast ? "" : '<span class="dep-arrow">→</span>'}`;
const isFirst = i === 0;
const label = isFirst
? escapeHtml(node)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The first hop is also a package on npm - no need to skip it. Remove the isFirst check and just always wrap node in the link.

Comment thread src/output/html-reporter.ts Outdated
<td><span class="expand-icon" id="icon-${idx}">▶</span></td>
<td><div class="pkg-name">${escapeHtml(finding.package)}</div><div class="pkg-version">${escapeHtml(finding.version)}</div></td>
<td>${fixHtml}</td>
<td><div class="pkg-name"><a href="https://www.npmjs.com/package/${escapeHtml(finding.package)}" target="_blank" rel="noopener noreferrer">${escapeHtml(finding.package)}</a></div><div class="pkg-version">${escapeHtml(finding.version)}</div></td> <td>${fixHtml}</td>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<td>${fixHtml}</td> got merged onto the end of the line above. Put it on its own line.

@sonukapoor sonukapoor left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good progress on removing the isFirst skip and using encodeURIComponent in the href. Two functional bugs to fix before merge.

Comment thread src/output/html-reporter.ts Outdated
}).join("")
: `<span class="dep-node">${escapeHtml(finding.package)}</span>`;
&& finding.dependencyPaths[0].map((node, i, arr) => {
const isLast = i === arr.length - 1;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching from a ternary to && dropped the fallback. When finding.dependencyPaths.length === 0, depPathHtml is now false, which renders as the literal text false in the HTML. Restore the ternary:

const depPathHtml = finding.dependencyPaths.length > 0
  ? finding.dependencyPaths[0].map((node, i, arr) => {
      const isLast = i === arr.length - 1;
      return `<a href="https://www.npmjs.com/package/${encodeURIComponent(node)}" target="_blank" rel="noopener noreferrer" class="dep-node${isLast ? " vulnerable" : "}">${escapeHtml(node)}</a>${isLast ? "" : '<span class="dep-arrow">→</span>'}`;
    }).join("")
  : `<span class="dep-node">${escapeHtml(finding.package)}</span>`;

const label = escapeHtml(node);

return `<a href="https://www.npmjs.com/package/${encodeURIComponent(node)}" target="_blank" rel="noopener noreferrer" class="dep-node${isLast ? " vulnerable" : ""}">${label}</a>${isLast ? "" : '<span class="dep-arrow">→</span>'}`;})

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing .join("") - .map() returns an Array, and when you interpolate it into the template string it joins with commas by default. Add .join("") after the closing }).

Comment thread src/output/html-reporter.ts Outdated
<td><span class="expand-icon" id="icon-${idx}">▶</span></td>
<td><div class="pkg-name">${escapeHtml(finding.package)}</div><div class="pkg-version">${escapeHtml(finding.version)}</div></td>
<td>${fixHtml}</td>
<td><div class="pkg-name"><a href="https://www.npmjs.com/package/${escapeHtml(finding.package)}" target="_blank" rel="noopener noreferrer">${escapeHtml(finding.package)}</a></div><div class="pkg-version">${escapeHtml(finding.version)}</div></td> <td>${fixHtml}</td>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<td>${fixHtml}</td> is still on the same line as the pkg-name cell. Put it on its own line.

@sonukapoor sonukapoor left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting close - the ternary fallback is back, .join("") is there, and the dep-path links look right. Two more things to fix.

Comment thread src/output/html-reporter.ts Outdated
import type { MaintenanceFinding } from "../maintenance/types.js";
import { UNVERIFIED_PARENT_UPGRADE_NOTE, type SuggestedFixCommandPlan } from "../remediation/fix-commands.js";
import { getCompletenessImpact } from "../scan/completeness.js";
import { escape } from "node:querystring";

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import is unused - escape from node:querystring is never called anywhere in the file. Remove it (it will also fail the build if noUnusedLocals is enabled).

@@ -586,10 +587,9 @@ export function renderFindingRow(finding: SerializedFinding, idx: number, skippe
const rootDepsHtml = finding.rootDependencies.length > 0
? finding.rootDependencies.map(name => `<span class="root-dep">${escapeHtml(name)}</span>`).join(", ")

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pkg-name cell link uses escapeHtml() in the href, but it should use encodeURIComponent() - same as you're doing for dep path nodes above. escapeHtml converts & to &amp; which breaks the URL. For scoped packages like @babel/core this would produce href="https://www.npmjs.com/package/@babel/core" correctly by accident, but the right function for URL encoding is encodeURIComponent:

<a href="https://www.npmjs.com/package/${encodeURIComponent(finding.package)}" ...>

@sonukapoor sonukapoor left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both issues resolved - encodeURIComponent is used correctly throughout (dep path nodes, pkg-name cell, chain proof hops), and the unused escape import is gone. Merging.

@bharatmalik-cs

bharatmalik-cs commented Aug 23, 2026 via email

Copy link
Copy Markdown
Author

@sonukapoor sonukapoor left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice addition - clickable npm links are a small but genuinely useful touch in the HTML report.

@sonukapoor
sonukapoor merged commit c60a064 into OWASP:main Aug 24, 2026
6 checks passed
@sonukapoor

Copy link
Copy Markdown
Collaborator

Merged - thank you @bharatmalik-cs!

@sonukapoor sonukapoor mentioned this pull request Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(html-report): make package names in dependency path clickable npm links

2 participants